home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Tools 2
/
Amiga Tools 2.iso
/
tools
/
jade
/
src
/
streams.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-03-09
|
31KB
|
1,345 lines
/* streams.c -- Lisp stream handling
Copyright (C) 1993, 1994 John Harper <jsh@ukc.ac.uk>
This file is part of Jade.
Jade is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
Jade is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jade; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
/* These are the Lisp objects which are classed as streams:
FILE: [rw]
MARK: [rw] advance pos attribute of mark afterwards
BUFFER: [rw] from cursor pos
(NUMBER . STRING): [r] from the NUMBER'th char of STRING
(STRING . ACTUAL-LENGTH): [w] to after INDEX
(BUFFER . POS): [rw] from BUFFER, POS is advanced
(BUFFER . t): [w] end of BUFFER
FUNCTION: [rw] call FUNCTION, when reading FUNCTION is expected to
return the next character, when writing it is called with
one arg, either character or string.
PROCESS: [w] write to the stdin of the PROCESS if it's running
t: [w] display in status line */
#include "jade.h"
#include "jade_protos.h"
#include "regexp/regexp.h"
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdlib.h>
#ifdef NEED_MEMORY_H
# include <memory.h>
#endif
_PR int stream_getc(VALUE);
_PR int stream_ungetc(VALUE, int);
_PR int stream_putc(VALUE, int);
_PR int stream_puts(VALUE, u_char *, int, bool);
_PR int stream_read_esc(VALUE, int *);
_PR void stream_put_cntl(VALUE, int);
_PR void file_sweep(void);
_PR int file_cmp(VALUE, VALUE);
_PR void file_prin(VALUE, VALUE);
_PR void streams_init(void);
_PR void streams_kill(void);
static int
pos_getc(TX *tx, POS *pos)
{
int c = EOF;
if(pos->pos_Line < tx->tx_NumLines)
{
LINE *ln = tx->tx_Lines + pos->pos_Line;
if(pos->pos_Col >= (ln->ln_Strlen - 1))
{
if(++pos->pos_Line == tx->tx_NumLines)
{
--pos->pos_Line;
return(EOF);
}
pos->pos_Col = 0;
return('\n');
}
c = ln->ln_Line[pos->pos_Col++];
}
return(c);
}
static int
pos_putc(TX *tx, POS *pos, int c)
{
int rc = EOF;
if(!read_only(tx) && pad_pos(tx, pos))
{
u_char tmps[2];
tmps[0] = (u_char)c;
tmps[1] = 0;
if(iscntrl(c))
{
if(insert_string(tx, tmps, 1, pos))
rc = 1;
}
else
{
POS start = *pos;
if(insert_str_n(tx, tmps, 1, pos))
{
undo_record_insertion(tx, &start, pos);
flag_insertion(tx, &start, pos);
rc = 1;
}
}
}
return(rc);
}
static int
pos_puts(TX *tx, POS *pos, u_char *buf, int bufLen)
{
int rc = EOF;
if(!read_only(tx) && pad_pos(tx, pos))
{
if(insert_string(tx, buf, bufLen, pos))
rc = bufLen;
}
return(rc);
}
int
stream_getc(VALUE stream)
{
int c = EOF;
if(NILP(stream)
&& !(stream = cmd_symbol_value(sym_standard_input, sym_nil)))
return(c);
switch(VTYPE(stream))
{
VALUE res;
int oldgci;
case V_File:
if(VFILE(stream)->lf_Name)
c = getc(VFILE(stream)->lf_File);
break;
case V_Mark:
if(!VMARK(stream)->mk_Resident)
cmd_signal(sym_invalid_stream, list_2(stream, MKSTR("Marks used as streams must be resident")));
else
c = pos_getc(VMARK(stream)->mk_File.tx,
&VPOS(VMARK(stream)->mk_Pos));
break;
case V_TX:
c = pos_getc(VTX(stream), get_tx_cursor(VTX(stream)));
break;
case V_Cons:
res = VCAR(stream);
if(NUMBERP(res) && STRINGP(VCDR(stream)))
{
c = (int)VSTR(VCDR(stream))[VNUM(res)];
if(c)
VCAR(stream) = make_number(VNUM(res) + 1);
else
c = EOF;
break;
}
else if(BUFFERP(res) && POSP(VCDR(stream)))
{
c = pos_getc(VTX(res), &VPOS(VCDR(stream)));
break;
}
else if(res != sym_lambda)
{
cmd_signal(sym_invalid_stream, LIST_1(stream));
break;
}
/* FALL THROUGH */
case V_Symbol:
oldgci = gc_inhibit;
gc_inhibit = TRUE;
if((res = call_lisp0(stream)) && NUMBERP(res))
c = VNUM(res);
gc_inhibit = oldgci;
break;
#ifdef HAVE_SUBPROCESSES
case V_Process:
cmd_signal(sym_invalid_stream, list_2(stream, MKSTR("Processes are not input streams")));
break;
#endif
default:
cmd_signal(sym_invalid_stream, LIST_1(stream));
}
return(c);
}
/* Puts back one character, it will be read next call to streamgetc on
this stream.
Note that some types of stream don't actually use c, they just rewind
pointers.
Never call this unless you *have* *successfully* read from the stream
previously. (few checks are performed here, I assume they were made in
streamgetc()). */
#define POS_UNGETC(p,tx) \
if(--((p)->pos_Col) < 0) \
{ \
(p)->pos_Line--; \
(p)->pos_Col = (tx)->tx_Lines[(p)->pos_Line].ln_Strlen - 1; \
}
int
stream_ungetc(VALUE stream, int c)
{
int rc = FALSE;
if(NILP(stream)
&& !(stream = cmd_symbol_value(sym_standard_input, sym_nil)))
return(rc);
switch(VTYPE(stream))
{
POS *pos;
VALUE tmp;
int oldgci;
case V_File:
if(ungetc(c, VFILE(stream)->lf_File) != EOF)
rc = TRUE;
break;
case V_Mark:
pos = &VPOS(VMARK(stream)->mk_Pos);
POS_UNGETC(pos, VMARK(stream)->mk_File.tx)
rc = TRUE;
break;
case V_TX:
pos = get_tx_cursor(VTX(stream));
POS_UNGETC(pos, VTX(stream))
rc = TRUE;
break;
case V_Cons:
tmp = VCAR(stream);
if(NUMBERP(tmp) && STRINGP(VCDR(stream)))
{
VCAR(stream) = make_number(VNUM(tmp) - 1);
rc = TRUE;
break;
}
else if(BUFFERP(tmp) && POSP(VCDR(stream)))
{
POS_UNGETC(&VPOS(VCDR(stream)), VTX(tmp));
rc = TRUE;
break;
}
/* FALL THROUGH */
case V_Symbol:
tmp = make_number(c);
oldgci = gc_inhibit;
gc_inhibit = TRUE;
if((tmp = call_lisp1(stream, tmp)) && !NILP(tmp))
rc = TRUE;
gc_inhibit = oldgci;
break;
}
return(rc);
}
int
stream_putc(VALUE stream, int c)
{
int rc = 0;
if(NILP(stream)
&& !(stream = cmd_symbol_value(sym_standard_output, sym_nil)))
return(rc);
switch(VTYPE(stream))
{
VALUE args, res, new;
int len;
u_char tmps[2];
POS pos;
case V_File:
if(VFILE(stream)->lf_Name)
{
if(putc(c, VFILE(stream)->lf_File) != EOF)
rc = 1;
}
break;
case V_Mark:
if(!VMARK(stream)->mk_Resident)
cmd_signal(sym_invalid_stream, list_2(stream, MKSTR("Marks used as streams must be resident")));
else
{
pos = VPOS(VMARK(stream)->mk_Pos);
rc = pos_putc(VMARK(stream)->mk_File.tx, &pos, c);
}
break;
case V_TX:
pos = *(get_tx_cursor(VTX(stream)));
rc = pos_putc(VTX(stream), &pos, c);
break;
case V_Cons:
args = VCAR(stream);
if(VTYPEP(args, V_DynamicString) && NUMBERP(VCDR(stream)))
{
int actuallen = VNUM(VCDR(stream));
len = STRING_LEN(args);
if(len + 1 >= actuallen)
{
int newlen = actuallen < 16 ? 32 : actuallen * 2;
new = make_string(newlen + 1);
if(!new)
break;
memcpy(VSTR(new), VSTR(args), len);
VCAR(stream) = new;
VCDR(stream) = make_number(newlen);
args = new;
}
VSTR(args)[len] = (u_char)c;
VSTR(args)[len+1] = 0;
set_string_len(args, len + 1);
rc = 1;
break;
}
else if(BUFFERP(args))
{
if(POSP(VCDR(stream)))
rc = pos_putc(VTX(args), &VPOS(VCDR(stream)), c);
else
{
pos.pos_Line = VTX(args)->tx_NumLines - 1;
pos.pos_Col = VTX(args)->tx_Lines[pos.pos_Line].ln_Strlen - 1;
rc = pos_putc(VTX(args), &pos, c);
}
break;
}
else if(args != sym_lambda)
{
cmd_signal(sym_invalid_stream, LIST_1(stream));
break;
}
/* FALL THROUGH */
case V_Symbol:
if(stream == sym_t)
{
if(curr_vw->vw_Flags & VWFF_MESSAGE)
{
VW *vw = curr_vw;
u_char *s;
s = str_dupn(vw->vw_Message, vw->vw_MessageLen + 1);
if(s)
{
s[vw->vw_MessageLen++] = c;
s[vw->vw_MessageLen] = 0;
str_free(vw->vw_Message);
vw->vw_Message = s;
vw->vw_Flags |= VWFF_MESSAGE | VWFF_REFRESH_STATUS;
}
}
else
{
tmps[0] = (u_char)c;
tmps[1] = 0;
messagen(tmps, 1);
}
rc = 1;
}
else
{
int oldgci = gc_inhibit;
gc_inhibit = TRUE;
if((res = call_lisp1(stream, make_number(c))) && !NILP(res))
rc = 1;
gc_inhibit = oldgci;
}
break;
#ifdef HAVE_SUBPROCESSES
case V_Process:
tmps[0] = (u_char)c;
tmps[1] = 0;
rc = write_to_process(stream, tmps, 1);
break;
#endif
default:
cmd_signal(sym_invalid_stream, LIST_1(stream));
}
return(rc);
}
int
stream_puts(VALUE stream, u_char *buf, int bufLen, bool isValString)
{
int rc = 0;
if(NILP(stream)
&& !(stream = cmd_symbol_value(sym_standard_output, sym_nil)))
return(rc);
if(bufLen == -1)
bufLen = isValString ? STRING_LEN(VAL(STRING_HDR(buf))) : strlen(buf);
switch(VTYPE(stream))
{
VALUE args, res, new;
int len, newlen;
POS pos;
case V_File:
if(VFILE(stream)->lf_Name)
{
if((rc = fwrite(buf, 1, bufLen, VFILE(stream)->lf_File)) == bufLen)
rc = bufLen;
}
break;
case V_Mark:
if(!VMARK(stream)->mk_Resident)
cmd_signal(sym_invalid_stream, list_2(stream, MKSTR("Marks used as streams must be resident")));
else
{
pos = VPOS(VMARK(stream)->mk_Pos);
rc = pos_puts(VMARK(stream)->mk_File.tx, &pos, buf, bufLen);
}
break;
case V_TX:
pos = *(get_tx_cursor(VTX(stream)));
rc = pos_puts(VTX(stream), &pos, buf, bufLen);
break;
case V_Cons:
args = VCAR(stream);
if(VTYPEP(args, V_DynamicString) && NUMBERP(VCDR(stream)))
{
int actuallen = VNUM(VCDR(stream));
len = STRING_LEN(args);
newlen = len + bufLen + 1;
if(actuallen <= newlen)
{
register int tmp = actuallen < 16 ? 32 : actuallen * 2;
if(tmp > newlen)
newlen = tmp;
new = make_string(newlen + 1);
if(!new)
break;
memcpy(VSTR(new), VSTR(args), len);
VCAR(stream) = new;
VCDR(stream) = make_number(newlen);
args = new;
}
#if 1
memcpy(VSTR(args) + len, buf, bufLen);
VSTR(args)[len + bufLen] = 0;
#else
strcpy(VSTR(args) + len, buf);
#endif
set_string_len(args, len + bufLen);
rc = bufLen;
break;
}
else if(BUFFERP(args))
{
if(POSP(VCDR(stream)))
rc = pos_puts(VTX(args), &VPOS(VCDR(stream)), buf, bufLen);
else
{
pos.pos_Line = VTX(args)->tx_NumLines - 1;
pos.pos_Col = VTX(args)->tx_Lines[pos.pos_Line].ln_Strlen - 1;
rc = pos_puts(VTX(args), &pos, buf, bufLen);
}
break;
}
else if(args != sym_lambda)
{
cmd_signal(sym_invalid_stream, LIST_1(stream));
break;
}
/* FALL THROUGH */
case V_Symbol:
if(stream == sym_t)
{
if(curr_vw->vw_Flags & VWFF_MESSAGE)
{
VW *vw = curr_vw;
u_char *s;
newlen = vw->vw_MessageLen + bufLen;
s = str_dupn(vw->vw_Message, newlen);
if(s)
{
memcpy(s + vw->vw_MessageLen, buf, bufLen);
s[newlen] = 0;
str_free(vw->vw_Message);
vw->vw_Message = s;
vw->vw_MessageLen = newlen;
vw->vw_Flags |= VWFF_MESSAGE | VWFF_REFRESH_STATUS;
}
}
else
messagen(buf, bufLen);
rc = bufLen;
}
else
{
int oldgci = gc_inhibit;
if(isValString)
args = VAL(STRING_HDR(buf));
else
args = string_dupn(buf, bufLen);
gc_inhibit = TRUE;
if((res = call_lisp1(stream, args)) && !NILP(res))
{
if(NUMBERP(res))
rc = VNUM(res);
else
rc = bufLen;
}
gc_inhibit = oldgci;
}
break;
#ifdef HAVE_SUBPROCESSES
case V_Process:
rc = write_to_process(stream, buf, bufLen);
break;
#endif
default:
cmd_signal(sym_invalid_stream, LIST_1(stream));
}
return(rc);
}
/* Read an escape sequence from STREAM. C_P should contain the first
character of the escape *not* the escape character. Supported sequences
are,
n newline
r carriage return
f form feed
t horizontal tab
v vertical tab
a bell
^C control code of C
012 octal character code
x12 hex character code
Otherwise the character is returned as-is. */
int
stream_read_esc(VALUE stream, int *c_p)
{
u_char c;
switch(*c_p)
{
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 'f':
c = '\f';
break;
case 't':
c = '\t';
break;
case 'v':
c = '\v';
break;
case 'a':
c = '\a';
break;
case '^':
c = toupper(stream_getc(stream)) ^ 0x40;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
c = *c_p - '0';
*c_p = stream_getc(stream);
if((*c_p >= '0') && (*c_p <= '7'))
{
c = (c * 8) + (*c_p - '0');
*c_p = stream_getc(stream);
if((*c_p >= '0') && (*c_p <= '7'))
{
c = (c * 8) + (*c_p - '0');
break;
}
else
return(c);
}
else
return(c);
case 'x':
c = 0;
while(1)
{
*c_p = stream_getc(stream);
if(!isxdigit(*c_p))
return(c);
if((*c_p >= '0') && (*c_p <= '9'))
c = (c * 16) + (*c_p - '0');
else
c = (c * 16) + (toupper(*c_p) - 'A') + 10;
}
default:
c = *c_p;
}
*c_p = stream_getc(stream);
return(c);
}
/* Print an escape sequence for the character C to STREAM. */
void
stream_put_cntl(VALUE stream, int c)
{
u_char buff[40];
u_char *buf = buff + 1;
buff[0] = V_StaticString;
switch(c)
{
case '\n':
strcpy(buf, "\\n");
break;
case '\t':
strcpy(buf, "\\t");
break;
case '\r':
strcpy(buf, "\\r");
break;
case '\f':
strcpy(buf, "\\f");
break;
case '\a':
strcpy(buf, "\\a");
break;
default:
if(c <= 0x3f)
sprintf(buf, "\\^%c", c + 0x40);
else
sprintf(buf, "\\%o", (int)c);
break;
}
stream_puts(stream, buf, -1, TRUE);
}
_PR VALUE cmd_write(VALUE stream, VALUE data, VALUE len);
DEFUN("write", cmd_write, subr_write, (VALUE stream, VALUE data, VALUE len), V_Subr3, DOC_write) /*
::doc:write::
write STREAM DATA [LENGTH]
Writes DATA, which can either be a string or a character, to the stream
STREAM, returning the number of characters actually written. If DATA is
a string LENGTH can define how many characters to write.
::end:: */
{
int actual;
switch(VTYPE(data))
{
bool vstring;
case V_Number:
actual = stream_putc(stream, VNUM(data));
break;
case V_StaticString:
case V_DynamicString:
if(NUMBERP(len))
{
actual = VNUM(len);
if(actual > STRING_LEN(data))
return(signal_arg_error(len, 3));
if(actual == STRING_LEN(data))
vstring = TRUE;
else
vstring = FALSE;
}
else
{
actual = STRING_LEN(data);
vstring = TRUE;
}
actual = stream_puts(stream, VSTR(data), actual, vstring);
break;
default:
return(signal_arg_error(data, 2));
}
return(make_number(actual));
}
_PR VALUE cmd_read_char(VALUE stream);
DEFUN("read-char", cmd_read_char, subr_read_char, (VALUE stream), V_Subr1, DOC_read_char) /*
::doc:read_char::
read-char STREAM
Reads the next character from the input-stream STREAM, if no more characters
are available returns nil.
::end:: */
{
int rc;
if((rc = stream_getc(stream)) != EOF)
return(make_number(rc));
return(sym_nil);
}
_PR VALUE cmd_read_line(VALUE stream);
DEFUN("read-line", cmd_read_line, subr_read_line, (VALUE stream), V_Subr1, DOC_read_line) /*
::doc:read_line::
read-line STREAM
Read one line of text from STREAM.
::end:: */
{
u_char buf[400];
if(FILEP(stream))
{
/* Special case for file streams. We can read a line in one go. */
if(VFILE(stream)->lf_Name && fgets(buf, 400, VFILE(stream)->lf_File))
return(string_dup(buf));
return(sym_nil);
}
else
{
u_char *bufp = buf;
int len = 0, c;
while((c = stream_getc(stream)) != EOF)
{
*bufp++ = (u_char)c;
if((++len >= 399) || (c == '\n'))
break;
}
if(len == 0)
return(sym_nil);
return(string_dupn(buf, len));
}
}
_PR VALUE cmd_copy_stream(VALUE source, VALUE dest);
DEFUN("copy-stream", cmd_copy_stream, subr_copy_stream, (VALUE source, VALUE dest), V_Subr2, DOC_copy_stream) /*
::doc:copy_stream::
copy-stream SOURCE-STREAM DEST-STREAM
Copy all characters from SOURCE-STREAM to DEST-STREAM until an EOF is read.
::end:: */
{
int len = 0, i = 0, c;
u_char buff[402];
u_char *buf = buff + 1;
buff[0] = V_StaticString;
while((c = stream_getc(source)) != EOF)
{
if(i == 400)
{
buf[i] = 0;
if(stream_puts(dest, buf, i, TRUE) == EOF)
break;
i = 0;
}
else
buf[i++] = c;
len++;
TEST_INT;
if(INT_P)
return(NULL);
}
if(i > 0)
{
buff[i] = 0;
stream_puts(dest, buf, i, TRUE);
}
if(len)
return(make_number(len));
return(sym_nil);
}
_PR VALUE cmd_read(VALUE);
DEFUN("read", cmd_read, subr_read, (VALUE stream), V_Subr1, DOC_read) /*
::doc:read::
read [STREAM]
Reads one lisp-object from the input-stream STREAM (or the value of the
variable `standard-input' if STREAM is unspecified) and return it.
::end:: */
{
VALUE res;
int c;
if(NILP(stream)
&& !(stream = cmd_symbol_value(sym_standard_input, sym_nil)))
{
signal_arg_error(stream, 1);
return(NULL);
}
c = stream_getc(stream);
if(c == EOF)
res = cmd_signal(sym_end_of_stream, LIST_1(stream));
else
res = readl(stream, &c);
/* If an error occurred leave stream where it is. */
if(res && c != EOF)
stream_ungetc(stream, c);
return(res);
}
_PR VALUE cmd_print(VALUE, VALUE);
DEFUN("print", cmd_print, subr_print, (VALUE obj, VALUE stream), V_Subr2, DOC_print) /*
::doc:print::
print OBJECT [STREAM]
First outputs a newline, then prints a text representation of OBJECT to
STREAM (or the contents of the variable `standard-output') in a form suitable
for `read'.
::end:: */
{
if(NILP(stream)
&& !(stream = cmd_symbol_value(sym_standard_output, sym_nil)))
{
signal_arg_error(stream, 1);
return(NULL);
}
stream_putc(stream, '\n');
print_val(stream, obj);
return(obj);
}
_PR VALUE cmd_prin1(VALUE, VALUE);
DEFUN("prin1", cmd_prin1, subr_prin1, (VALUE obj, VALUE stream), V_Subr2, DOC_prin1) /*
::doc:prin1::
prin1 OBJECT [STREAM]
Prints a text representation of OBJECT to STREAM (or the contents of the
variable `standard-output') in a form suitable for `read'.
::end:: */
{
if(NILP(stream)
&& !(stream = cmd_symbol_value(sym_standard_output, sym_nil)))
{
signal_arg_error(stream, 1);
return(NULL);
}
print_val(stream, obj);
return(obj);
}
_PR VALUE cmd_princ(VALUE, VALUE);
DEFUN("princ", cmd_princ, subr_princ, (VALUE obj, VALUE stream), V_Subr2, DOC_princ) /*
::doc:princ::
princ OBJECT [STREAM]
Prints a text representation of OBJECT to STREAM (or the contents of the
variable standard-output), no strange characters are quoted and no quotes
are printed around strings.
::end:: */
{
if(NILP(stream)
&& !(stream = cmd_symbol_value(sym_standard_output, sym_nil)))
{
signal_arg_error(stream, 1);
return(NULL);
}
princ_val(stream, obj);
return(obj);
}
_PR VALUE cmd_format(VALUE);
DEFUN("format", cmd_format, subr_format, (VALUE args), V_SubrN, DOC_format) /*
::doc:format::
format STREAM FORMAT-STRING ARGS...
Writes a string created from the format specification FORMAT-STRING and
the argument-values ARGS to the stream, STREAM. If STREAM is nil a string
is created and returned.
FORMAT-STRING is a template for the result, any `%' characters introduce
a substitution, using the next unused ARG. These format specifiers are
implemented:
d print next ARG as decimal integer
x print next ARG as hexadecimal integer
o print next ARG in octal
c print next ARG as ASCII character
s unquoted representation (as from `princ') of next ARG
S normal print'ed representation of next ARG
% literal percentage character
::end:: */
{
u_char *fmt, *last_fmt;
bool mk_str;
VALUE stream = ARG2;
u_char c;
DECLARE1(stream, STRINGP);
fmt = VSTR(stream);
stream = ARG1;
if(NILP(stream))
{
stream = cmd_cons(string_dupn("", 0), make_number(0));
mk_str = TRUE;
}
else
mk_str = FALSE;
args = move_down_list(args, 2);
last_fmt = fmt;
while((c = *fmt++))
{
if(c == '%')
{
u_char tbuf[40], nfmt[4];
VALUE val = ARG1;
if(last_fmt != fmt - 1)
stream_puts(stream, last_fmt, fmt - last_fmt - 1, FALSE);
switch(c = *fmt++)
{
case 'd':
case 'x':
case 'o':
case 'c':
nfmt[0] = '%';
nfmt[1] = 'l';
nfmt[2] = c;
nfmt[3] = 0;
sprintf(tbuf, nfmt, NUMBERP(val) ? VNUM(val) : (long)val);
stream_puts(stream, tbuf, -1, FALSE);
break;
case 's':
princ_val(stream, val);
break;
case 'S':
print_val(stream, val);
break;
case '%':
stream_putc(stream, '%');
break;
}
if(c != '%')
args = move_down_list(args, 1);
last_fmt = fmt;
}
}
if(last_fmt != fmt - 1)
stream_puts(stream, last_fmt, fmt - last_fmt - 1, FALSE);
if(mk_str)
{
if(STRING_LEN(VCAR(stream)) != VNUM(VCDR(stream)))
{
/* Truncate the stream to it's actual length. */
stream = cmd_copy_sequence(VCAR(stream));
}
else
stream = VCAR(stream);
}
return(stream);
}
_PR VALUE cmd_make_string_input_stream(VALUE string, VALUE start);
DEFUN("make-string-input-stream", cmd_make_string_input_stream, subr_make_string_input_stream, (VALUE string, VALUE start), V_Subr2, DOC_make_string_input_stream) /*
::doc:make_string_input_stream::
make-string-input-stream STRING [START]
Returns a input stream, it will supply, in order, the characters in STRING,
starting from START (or the beginning of the string).
::end:: */
{
DECLARE1(string, STRINGP);
return(cmd_cons(NUMBERP(start) ? start : make_number(0), string));
}
_PR VALUE cmd_make_string_output_stream(void);
DEFUN("make-string-output-stream", cmd_make_string_output_stream, subr_make_string_output_stream, (void), V_Subr0, DOC_make_string_output_stream) /*
::doc:make_string_output_stream::
make-string-output-stream
Returns an output stream which will accumulate the characters written to
it for the use of the `get-output-stream-string' function.
::end:: */
{
return(cmd_cons(string_dupn("", 0), make_number(0)));
}
_PR VALUE cmd_get_output_stream_string(VALUE strm);
DEFUN("get-output-stream-string", cmd_get_output_stream_string, subr_get_output_stream_string, (VALUE strm), V_Subr1, DOC_get_output_stream_string) /*
::doc:get_output_stream_string::
get-output-stream-string STRING-OUTPUT-STREAM
Returns a string containing the characters written to the stream STRING-
OUTPUT-STREAM (created by `make-string-output-stream'). The stream is then
reset so that the next call to this function with this stream will only
return the new characters.
::end:: */
{
VALUE string;
if(!CONSP(strm) || !STRINGP(VCAR(strm)) || !NUMBERP(VCDR(strm)))
return(signal_arg_error(strm, 1));
if(STRING_LEN(VCAR(strm)) != VNUM(VCDR(strm)))
{
/* Truncate the string to it's actual length. */
string = cmd_copy_sequence(VCAR(strm));
}
else
string = VCAR(strm);
/* Reset the stream. */
VCAR(strm) = string_dupn("", 0);
VCDR(strm) = make_number(0);
return(string);
}
_PR VALUE cmd_streamp(VALUE arg);
DEFUN("streamp", cmd_streamp, subr_streamp, (VALUE arg), V_Subr1, DOC_streamp) /*
::doc:streamp::
streamp ARG
Returns t if ARG is a stream.
::end:: */
{
VALUE res = sym_nil;
switch(VTYPE(arg))
{
VALUE car, cdr;
case V_File:
case V_Buffer:
case V_Mark:
case V_Process:
case V_Symbol:
res = sym_t;
break;
case V_Cons:
car = VCAR(arg);
cdr = VCDR(arg);
if((car == sym_lambda)
|| (BUFFERP(car) && (POSP(cdr) || (cdr == sym_t)))
|| (NUMBERP(car) && STRINGP(cdr))
|| (STRINGP(car) && NUMBERP(cdr)))
res = sym_t;
break;
}
return(res);
}
static LFile *lfile_chain;
void
file_sweep(void)
{
LFile *lf = lfile_chain;
lfile_chain = NULL;
while(lf)
{
LFile *nxt = lf->lf_Next;
if(!GC_MARKEDP(VAL(lf)))
{
if(lf->lf_Name && !(lf->lf_Flags & LFF_DONT_CLOSE))
fclose(lf->lf_File);
str_free(lf);
}
else
{
GC_CLR(VAL(lf));
lf->lf_Next = lfile_chain;
lfile_chain = lf;
}
lf = nxt;
}
}
int
file_cmp(VALUE v1, VALUE v2)
{
if(VTYPE(v1) == VTYPE(v2))
{
if(VFILE(v1)->lf_Name && VFILE(v2)->lf_Name)
return(!same_files(VSTR(VFILE(v1)->lf_Name), VSTR(VFILE(v2)->lf_Name)));
}
return(1);
}
void
file_prin(VALUE strm, VALUE obj)
{
stream_puts(strm, "#<file ", -1, FALSE);
if(VFILE(obj)->lf_Name)
{
stream_puts(strm, VSTR(VFILE(obj)->lf_Name), -1, FALSE);
stream_putc(strm, '>');
}
else
stream_puts(strm, "*unbound*>", -1, FALSE);
}
_PR VALUE cmd_open(VALUE name, VALUE modes, VALUE file);
DEFUN("open", cmd_open, subr_open, (VALUE name, VALUE modes, VALUE file), V_Subr3, DOC_open) /*
::doc:open::
open [FILE-NAME MODE-STRING] [FILE]
Opens a file called FILE-NAME with modes MODE-STRING (standard c-library
modes, ie `r' == read, `w' == write, etc). If FILE is given it is an
existing file object which is to be closed before opening the new file on it.
::end:: */
{
LFile *lf;
if(!FILEP(file))
{
lf = str_alloc(sizeof(LFile));
if(lf)
{
lf->lf_Next = lfile_chain;
lfile_chain = lf;
lf->lf_Type = V_File;
}
}
else
{
lf = VFILE(file);
if(lf->lf_Name && !(lf->lf_Flags & LFF_DONT_CLOSE))
fclose(lf->lf_File);
}
if(lf)
{
lf->lf_File = NULL;
lf->lf_Name = NULL;
lf->lf_Flags = 0;
if(STRINGP(name) && STRINGP(modes))
{
lf->lf_File = fopen(VSTR(name), VSTR(modes));
if(lf->lf_File)
{
lf->lf_Name = name;
#ifdef HAVE_UNIX
/*
* set close-on-exec for easy process fork()ing
*/
fcntl(fileno(lf->lf_File), F_SETFD, 1);
#endif
}
else
return(cmd_signal(sym_file_error, list_2(lookup_errno(), name)));
}
return(VAL(lf));
}
return(NULL);
}
_PR VALUE cmd_close(VALUE file);
DEFUN("close", cmd_close, subr_close, (VALUE file), V_Subr1, DOC_close) /*
::doc:close::
close FILE
Kills any association between object FILE and the file in the filesystem that
it has open.
::end:: */
{
DECLARE1(file, FILEP);
if(VFILE(file)->lf_Name && !(VFILE(file)->lf_Flags & LFF_DONT_CLOSE))
fclose(VFILE(file)->lf_File);
VFILE(file)->lf_File = NULL;
VFILE(file)->lf_Name = NULL;
return(file);
}
_PR VALUE cmd_flush_file(VALUE file);
DEFUN("flush-file", cmd_flush_file, subr_flush_file, (VALUE file), V_Subr1, DOC_flush_file) /*
::doc:flush_file::
flush-file FILE
Flushes any buffered output on FILE.
::end:: */
{
DECLARE1(file, FILEP);
if(VFILE(file)->lf_Name)
fflush(VFILE(file)->lf_File);
return(file);
}
_PR VALUE cmd_filep(VALUE arg);
DEFUN("filep", cmd_filep, subr_filep, (VALUE arg), V_Subr1, DOC_filep) /*
::doc:filep::
filep ARG
Returns t if ARG is a file object.
::end:: */
{
if(FILEP(arg))
return(sym_t);
return(sym_nil);
}
_PR VALUE cmd_file_bound_p(VALUE file);
DEFUN("file-bound-p", cmd_file_bound_p, subr_file_bound_p, (VALUE file), V_Subr1, DOC_file_bound_p) /*
::doc:file_bound_p::
file-bound-p FILE
Returns t if FILE is currently bound to a physical file.
::end:: */
{
DECLARE1(file, FILEP);
if(VFILE(file)->lf_Name)
return(sym_t);
return(sym_nil);
}
_PR VALUE cmd_file_binding(VALUE file);
DEFUN("file-binding", cmd_file_binding, subr_file_binding, (VALUE file), V_Subr1, DOC_file_binding) /*
::doc:file_binding::
file-binding FILE
Returns the name of the physical file FILE is bound to, or nil.
::end:: */
{
DECLARE1(file, FILEP);
if(VFILE(file)->lf_Name)
return(VFILE(file)->lf_Name);
return(sym_nil);
}
_PR VALUE cmd_file_eof_p(VALUE file);
DEFUN("file-eof-p", cmd_file_eof_p, subr_file_eof_p, (VALUE file), V_Subr1, DOC_file_eof_p) /*
::doc:file_eof_p::
file-eof-p FILE
Returns t when the end of FILE is reached.
::end:: */
{
DECLARE1(file, FILEP);
if(VFILE(file)->lf_Name && feof(VFILE(file)->lf_File))
return(sym_t);
return(sym_nil);
}
_PR VALUE cmd_read_file_until(VALUE file, VALUE re, VALUE nocase_p);
DEFUN("read-file-until", cmd_read_file_until, subr_read_file_until, (VALUE file, VALUE re, VALUE nocase_p), V_Subr3, DOC_read_file_until) /*
::doc:read_file_until::
read-file-until FILE REGEXP [IGNORE-CASE-P]
Read lines from the Lisp file object FILE until one matching the regular
expression REGEXP is found. The matching line is returned, or nil if no
lines match.
If IGNORE-CASE-P is non-nil the regexp matching is not case-sensitive.
::end:: */
{
regexp *prog;
u_char buf[400]; /* Fix this later. */
DECLARE1(file, FILEP);
DECLARE2(re, STRINGP);
if(!VFILE(file)->lf_Name)
return(cmd_signal(sym_bad_arg, list_2(MKSTR("File object is unbound"), file)));
prog = regcomp(VSTR(re));
if(prog)
{
int eflags = NILP(nocase_p) ? 0 : REG_NOCASE;
FILE *fh = VFILE(file)->lf_File;
VALUE res = sym_nil;
while(fgets(buf, 400, fh))
{
if(regexec2(prog, buf, eflags))
{
res = string_dup(buf);
break;
}
}
free(prog);
return(res);
}
return(NULL);
}
_PR VALUE cmd_stdin_file(void);
DEFUN("stdin-file", cmd_stdin_file, subr_stdin_file, (void), V_Subr0, DOC_stdin_file) /*
::doc:stdin_file::
stdin-file
Returns the file object representing the editor's standard input.
::end:: */
{
static VALUE stdin_file;
if(stdin_file)
return(stdin_file);
stdin_file = cmd_open(sym_nil, sym_nil, sym_nil);
VFILE(stdin_file)->lf_Name = MKSTR("<stdin>");
VFILE(stdin_file)->lf_File = stdin;
VFILE(stdin_file)->lf_Flags |= LFF_DONT_CLOSE;
mark_static(&stdin_file);
return(stdin_file);
}
_PR VALUE cmd_stdout_file(void);
DEFUN("stdout-file", cmd_stdout_file, subr_stdout_file, (void), V_Subr0, DOC_stdout_file) /*
::doc:stdout_file::
stdout-file
Returns the file object representing the editor's standard output.
::end:: */
{
static VALUE stdout_file;
if(stdout_file)
return(stdout_file);
stdout_file = cmd_open(sym_nil, sym_nil, sym_nil);
VFILE(stdout_file)->lf_Name = MKSTR("<stdout>");
VFILE(stdout_file)->lf_File = stdout;
VFILE(stdout_file)->lf_Flags |= LFF_DONT_CLOSE;
mark_static(&stdout_file);
return(stdout_file);
}
void
streams_init(void)
{
ADD_SUBR(subr_write);
ADD_SUBR(subr_read_char);
ADD_SUBR(subr_read_line);
ADD_SUBR(subr_copy_stream);
ADD_SUBR(subr_read);
ADD_SUBR(subr_print);
ADD_SUBR(subr_prin1);
ADD_SUBR(subr_princ);
ADD_SUBR(subr_format);
ADD_SUBR(subr_make_string_input_stream);
ADD_SUBR(subr_make_string_output_stream);
ADD_SUBR(subr_get_output_stream_string);
ADD_SUBR(subr_streamp);
ADD_SUBR(subr_open);
ADD_SUBR(subr_close);
ADD_SUBR(subr_flush_file);
ADD_SUBR(subr_filep);
ADD_SUBR(subr_file_bound_p);
ADD_SUBR(subr_file_binding);
ADD_SUBR(subr_file_eof_p);
ADD_SUBR(subr_read_file_until);
ADD_SUBR(subr_stdin_file);
ADD_SUBR(subr_stdout_file);
}
void
streams_kill(void)
{
LFile *lf = lfile_chain;
while(lf)
{
LFile *nxt = lf->lf_Next;
if(lf->lf_Name && !(lf->lf_Flags & LFF_DONT_CLOSE))
fclose(lf->lf_File);
str_free(lf);
lf = nxt;
}
lfile_chain = NULL;
}